#include "lioflat.h"

namespace Lio {

FlatComponent::FlatComponent():
		window(NULL),
		parent(NULL),
		commonFont(NULL),
		font(NULL),fontColor(0),
		visible(TRUE),disabled(FALSE),
		activeState(FALSE),overState(FALSE),
		borderLine(1),
		activeLine(1),
		borderColor(INT_BLACK),
		disabledColor(INT_GRAY),
		bgColor(INT_WHITE),
		cIndex(-1),zIndex(0),
		adjustLocation(0),
		adjustSize(FALSE),
		sticker(NULL),
		cursor(NULL),
		symbol(0),code(0),
		isStatic(FALSE),
		alignx(DIR_NONE),
		aligny(DIR_NONE)
{
	remChilds();
	margin.set(0,0);
	setBounds(0,0,0,0);
}
void FlatComponent::locate()
{
	if(adjustLocation&&sticker)
	{
		location.x=sticker->location.x+stickMargin.x;
		location.y=sticker->location.y+stickMargin.y;
		if(adjustLocation&DIR_RIGHT)
			location.x+=sticker->size.x;
		else if(adjustLocation&DIR_LEFT)
			location.x-=size.x;
		if(adjustLocation&DIR_DOWN)
			location.y+=sticker->size.y;
		else if(adjustLocation&DIR_UP)
			location.y-=size.y;
	}
	rect.set(location,size);
	if(parent)
	{
		rect.x+=parent->rect.x;
		rect.y+=parent->rect.y;
	}
}
void FlatComponent::copy(const FlatComponent &comp)
{
	location=comp.location;
	size=comp.size;
	margin=comp.margin;
	visible=comp.visible;
	disabled=comp.disabled;
	borderLine=comp.borderLine;
	activeLine=comp.activeLine;
	borderColor=comp.borderColor;
	bgColor=comp.bgColor;
	disabledColor=comp.disabledColor;
	fontColor=comp.fontColor;
	font=comp.font;
	stickMargin=comp.stickMargin;
	alignx=comp.alignx;
	aligny=comp.aligny;
	//adjustLocation=comp.adjustLocation;
	//adjustSize=comp.adjustSize;
}
void FlatComponent::remChilds()
{
	minZIndex=0;
	maxZIndex=-1;
	tabIndex=-1;
	activated=overed=NULL;
	last=NULL;
	FlatObject comp;
	for(comp=childs.first;comp;comp=comp->next)
	{
		comp->value->setOver(FALSE);
		comp->value->setActive(FALSE);
		comp->value->parent=NULL;
		comp->value->window=NULL;
	}
	childs.destroy();
}
BOOL FlatComponent::addChild(FlatComponent *child)
{
	if(!child||child->parent)
		return FALSE;
	if(!childs.add(child))
		return FALSE;
	child->cIndex=childs.length-1;
	child->zIndex=++maxZIndex;
	child->parent=this;
	if(!child->font)
		child->font=commonFont;
	last=child;
	return TRUE;
}
BOOL FlatComponent::stick(int dir, float x, float y, FlatComponent *comp)
{
	if(comp&&comp==this)
		return FALSE;
	stickMargin.set(x,y);
	adjustLocation=dir;
	sticker=comp;
	return TRUE;
}
void FlatComponent::build()
{
	if(!childs.length)
		return;
	Limit<float> x,y;
	FlatObject obj,prev;
	FlatComponent *comp;
	for(prev=NULL,obj=childs.first;obj;prev=obj,obj=obj->next)
	{
		comp=obj->value;
		if(!comp->sticker&&prev)
			comp->sticker=prev->value;
		comp->window=window;
		comp->resize();
		comp->locate();
		comp->build();
		x.add(comp->rect.x,comp->rect.x+comp->rect.width);
		y.add(comp->rect.y,comp->rect.y+comp->rect.height);
	}
	bounds.set(x.minValue,y.minValue,x.span(),y.span());
}
void FlatComponent::draw()
{
	if(!visible||!childs.length||!window)
		return;
	glPushAttrib(GL_SCISSOR_BIT);
	glEnable(GL_SCISSOR_TEST);
	int zIndex;
	FlatComponent *bottomComp;
	FlatObject comp;

	for(comp=childs.first,bottomComp=comp->value;comp;comp=comp->next)
	{
		if(comp->value->zIndex<bottomComp->zIndex)
			bottomComp=comp->value;
	}
	do
	{
		if(bottomComp->visible)
		{
			glScissor(	__int(bottomComp->rect.x-1),
						__int(window->height-bottomComp->rect.y-bottomComp->rect.height-1),
						__int(bottomComp->rect.width+2),
						__int(bottomComp->rect.height+2));
			bottomComp->draw();
		}
		zIndex=bottomComp->zIndex;
		for(comp=childs.first,bottomComp=NULL;comp;comp=comp->next)
		{
			if(comp->value->zIndex > zIndex)
			{
				bottomComp=comp->value;
				break;
			}
		}
		for(;comp;comp=comp->next)
		{
			if(	comp->value->zIndex>zIndex &&
				comp->value->zIndex<bottomComp->zIndex)
				bottomComp=comp->value;
		}
	}
	while(bottomComp);
	glPopAttrib();
}
BOOL FlatComponent::activate(FlatComponent* child)
{
	if(child!=activated)
	{
		if(child)
		{
			if(child->isStatic||!child->visible)
				return FALSE;
			child->setActive(TRUE);
		}
		if(activated)
			activated->setActive(FALSE);
		activated=child;
	}
	return TRUE;
}
void FlatComponent::over(FlatComponent* child)
{
	if(child!=overed)
	{
		if(overed)
			overed->setOver(FALSE);
		if(child)
			child->setOver(TRUE);
		overed=child;
	}
}
FlatComponent* FlatComponent::getChildByCoor(float x, float y, BOOL onlyVisible)
{
	FlatComponent * child = NULL;
	int zIndex=minZIndex-1;
	FlatObject comp;
	for(comp=childs.first;comp;comp=comp->next)
	{
		if((!onlyVisible||comp->value->visible)&&comp->value->rect.contain(x,y)&&comp->value->zIndex>zIndex)
		{
			child=comp->value;
			zIndex=comp->value->zIndex;
		}
	}
	return child;
}
FlatComponent* FlatComponent::getChildByID(int id)
{
	FlatObject comp;
	for(comp=childs.first;comp;comp=comp->next)
		if(comp->value->id==id)
			return comp->value;
	return NULL;
}
FlatComponent* FlatComponent::getChildBySymbol(_TCHAR symbol)
{
	if(symbol)
	{
		FlatObject comp;
		for(comp=childs.first;comp;comp=comp->next)
			if(comp->value->symbol==symbol)
				return comp->value;
	}
	return NULL;
}
FlatComponent* FlatComponent::getChildByCode(BYTE code)
{
	if(code)
	{
		FlatObject comp;
		for(comp=childs.first;comp;comp=comp->next)
			if(comp->value->code==code)
				return comp->value;
	}
	return NULL;
}
BOOL FlatComponent::input(BYTE type, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	if(!visible||!childs.length)
		return FALSE;
	FlatComponent *comp,*tmp;
	switch(type)
	{
		case KEY_INPUT_MSG:
		{
			if(activated&&!activated->disabled&&activated->input(type,msg,mouse,keyboard))
				return TRUE;
			if(keyboard.code==VK_TAB&&msg==WM_KEYDOWN)
			{
				BOOL rotate=FALSE;
				int step=1,length=childs.length;
				if(!length)
					return FALSE;
				if(keyboard.keys[VK_SHIFT])
					step=-1;
				if(activated)
					tabIndex=activated->cIndex;
				tabIndex+=step;
				for(int i=0;i<length;i++,tabIndex+=step)
				{
					if(tabIndex<0)
					{
						tabIndex=length-1;
						rotate=TRUE;
					}
					else if(tabIndex>=length)
					{
						tabIndex=0;
						rotate=TRUE;
					}
					comp=childs.objectAt(tabIndex)->value;
					if(activate(comp))
						return !rotate;
				}
			}
			else if(msg==WM_SYSCHAR)
			{
				if(comp=getChildBySymbol(keyboard.symbol))
				{
					activate(comp);
					comp->action();
					return TRUE;
				}
			}
			else if(msg==WM_KEYDOWN)
			{
				if(comp=getChildByCode(keyboard.code))
				{
					activate(comp);
					comp->action();
					return TRUE;
				}
			}
		}
		break;
		case MOUSE_INPUT_MSG:
		{
			if(comp=getChildByCoor((float)mouse.x,(float)mouse.y))
				tmp=comp;
			else
				tmp=activated;
			over(comp);
			if(overed&&overed->visible)
			{
				HCURSOR cursor=overed->getCursor();
				if(cursor)
					SetCursor(cursor);
			}
			if(msg==WM_LBUTTONDOWN)
				if(!activate(comp))
					return FALSE;
			return activated&&!activated->disabled&&activated->input(type,msg,mouse,keyboard);
		}
		break;	
	}
	return FALSE;
}

FlatScroll::FlatScroll():
			step(0),
			page(0),
			ratio(0),
			colorStart(INT_GRAY),
			colorMiddle(0),
			colorEnd(INT_GRAY)
{
	setDirection(DIR_RIGHT);
}
void FlatScroll::setDirection(int dir)
{
	direction=dir;
	if(dir&(DIR_DOWN|DIR_UP))
		setCursor(IDC_SIZENS);
	else
		setCursor(IDC_SIZEWE);
}
void FlatScroll::copy(const FlatScroll &scroll)
{
	FlatComponent::copy(scroll);
	direction=scroll.direction;
	ratio=scroll.ratio;
	step=scroll.step;
	page=scroll.page;
	colorStart=scroll.colorStart;
	colorMiddle=scroll.colorMiddle;
	colorEnd=scroll.colorEnd;
}
void FlatScroll::draw()
{
	drawLineIndicator(
		rect.x,rect.y,
		rect.width,rect.height,
		(float)ratio,
		borderLine,
		direction,
		colorStart, colorMiddle, colorEnd,
		bgColor, borderColor);
}
double FlatScroll::setRatio(double newRatio, BOOL event)
{
	if(newRatio>1)
		newRatio=1;
	else if(newRatio<0)
		newRatio=0;
	ratio=newRatio;
	if(event)
		call(FLAT_ACTION_DEFAULT);
	return ratio;
}
BOOL FlatScroll::input(BYTE, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	switch(msg)
	{
		case WM_KEYDOWN:
		{
			if(	(direction==DIR_RIGHT&&keyboard.code==VK_RIGHT)||
				(direction==DIR_LEFT&&keyboard.code==VK_LEFT)||
				(direction==DIR_UP&&keyboard.code==VK_UP)||
				(direction==DIR_DOWN&&keyboard.code==VK_DOWN))
			{
				setRatio(ratio+step);
				return TRUE;
			}
			if(	(direction==DIR_RIGHT&&keyboard.code==VK_LEFT)||
				(direction==DIR_LEFT&&keyboard.code==VK_RIGHT)||
				(direction==DIR_UP&&keyboard.code==VK_DOWN)||
				(direction==DIR_DOWN&&keyboard.code==VK_UP))
			{
				setRatio(ratio-step);
				return TRUE;
			}
			if(	((direction==DIR_RIGHT||direction==DIR_UP)&&keyboard.code==VK_PRIOR) ||
				((direction==DIR_LEFT||direction==DIR_DOWN)&&keyboard.code==VK_NEXT))
			{
				setRatio(ratio+page);
				return TRUE;
			}
			if(	((direction==DIR_RIGHT||direction==DIR_UP)&&keyboard.code==VK_NEXT) ||
				((direction==DIR_LEFT||direction==DIR_DOWN)&&keyboard.code==VK_PRIOR))
			{
				setRatio(ratio-page);
				return TRUE;
			}
		}
		case WM_MOUSEWHEEL:
		{
			if(direction==DIR_RIGHT||direction==DIR_UP)
			{
				setRatio(ratio+mouse.wheel*step);
				return TRUE;
			}
			if(direction==DIR_LEFT||direction==DIR_DOWN)
			{
				setRatio(ratio-mouse.wheel*step);
				return TRUE;
			}
		}
		break;
		case WM_MOUSEMOVE:
		{
			if(!mouse.lbutton)
				break;
		}
		case WM_LBUTTONDOWN:
		{
			switch(direction)
			{
			case DIR_LEFT:
				setRatio(double(rect.x+rect.width-mouse.x)/rect.width);
				break;
			case DIR_DOWN:
				setRatio(double(mouse.y-rect.y)/rect.height);
				break;
			case DIR_UP:
				setRatio(double(rect.y+rect.height-mouse.y)/rect.height);
				break;
			default:
				setRatio(double(mouse.x-rect.x)/rect.width);
			}
			return TRUE;
		}
	}
	return FALSE;
}
FlatButton::FlatButton():
		checked(FALSE),
		overColor(INT_WGRAY),
		pressColor(INT_GRAY),
		checkable(FALSE)
{
	setCursor(IDC_HAND);
	isStatic=FALSE;
}
void FlatButton::copy(const FlatButton &button)
{
	FlatLabel::copy(button);
	overColor=button.overColor;
	pressColor=button.pressColor;
	checkable=button.checkable;
}
void FlatButton::draw()
{
	DWORD color=bgColor;
	if(checked)
		color=pressColor;
	else if(overState)
		color=overColor;
	if(color)
		fillBox(rect.x,rect.y,rect.width,rect.height,color);
	float border=borderLine;
	if(activeState)
		border=activeLine;
	color=borderColor;
	if(disabled)
		color=disabledColor;
	if(color&&border)
		drawBox(rect.x,rect.y,rect.width,rect.height,color,border);
	FlatLabel::draw();
}
void FlatButton::action()
{
	if(checkable)
		checked=!checked;
	call(FLAT_ACTION_DEFAULT);
}
BOOL FlatButton::input(BYTE, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	if((msg==WM_KEYDOWN&&keyboard.code==VK_RETURN)||msg==WM_LBUTTONDOWN)
	{
		if(checkable)
		{
			checked=!checked;
			call(FLAT_ACTION_DEFAULT);
		}
		else
		{
			checked=TRUE;
		}
		return TRUE;
	}
	else if(!checkable&&checked&&
			((msg==WM_KEYUP&&keyboard.code==VK_RETURN)||
			(msg==WM_LBUTTONUP&&rect.contain((float)mouse.x,(float)mouse.y))))
	{
		checked=FALSE;
		call(FLAT_ACTION_DEFAULT);
		return TRUE;
	}
	return FALSE;
}
FlatText::FlatText():
		slide(0.3f),
		currentIndex(0),currentCoorX(0),slideCoorX(0),
		selectStartI(0),
		selectLeft(0),selectWidth(0),
		select(FALSE),
		carriageColor(INT_GRAY),
		selectColor(INT_WGRAY),
		tabSize(3),
		waitChar(FALSE),
		text(15)
{
	text.clear();
	setCursor(IDC_IBEAM);
}
void FlatText::draw()
{
	if(bgColor)
		fillBox(rect.x,rect.y,rect.width,rect.height,bgColor);
	if(selectColor&&selectLength())
		fillBox(rect.x-slideCoorX+margin.x+selectLeft,rect.y,selectWidth,rect.height,selectColor);
	if(font)
	{
		if(disabled)
			font->color=disabledColor;
		else if(fontColor)
			font->color=fontColor;
		font->x=rect.x+margin.x-slideCoorX;
		font->y=rect.y+font->textMetrix.tmAscent;
		int startIndex=font->getTextIndex(text,slideCoorX)+1;
		if(startIndex>0)
		{
			font->x+=font->getTextWidth(text,0,startIndex);
			font->print(text,startIndex,text.length()-startIndex);
		}
		else
		{
			font->print(text,0,text.length());
		}
	}
	if(carriageColor&&activeState)
		drawLine2(	rect.x+currentCoorX-slideCoorX+margin.x,rect.y,
					rect.x+currentCoorX-slideCoorX+margin.x,rect.y+rect.height,
					carriageColor,1.0f);
	float border=borderLine;
	if(activeState)
		border=activeLine;
	DWORD color=borderColor;
	if(disabled)
		color=disabledColor;
	if(color&&border)
		drawBox(rect.x,rect.y,rect.width,rect.height,color,border);
}
void FlatText::copy(const FlatText &textBox)
{
	FlatComponent::copy(textBox);
	carriageColor=textBox.carriageColor;
	tabSize=textBox.tabSize;
	format.copy(textBox.format);
}
BOOL FlatText::resize()
{
	if(!font||!font->loaded())
		return FALSE;
	if(adjustSize)
		size.x=size.y=0;
	if(!size.x)
		size.x=font->getTextWidth(text)+2*margin.x;
	if(!size.y)
		size.y=font->textMetrix.tmHeight+2*margin.y;
	return TRUE;
}
void FlatText::setText(CSTR newText, BOOL callback)
{
	text=newText?newText:"";
	currentCoorX=slideCoorX=0;
	selectStartI=currentIndex=0;
	applyTextChages(callback);
}
void FlatText::setCarriageCoor(float x, BOOL clearSelect)
{
	if(!font)
		return;
	int position,length=text.length();
	if(!length)
		return;
	float tmp,error=absval(margin.x-slideCoorX-x);
	for(position=1;position<=length;position++)
	{
		tmp=absval(font->getTextWidth(text,0,position)+margin.x-slideCoorX-x);
		if(tmp>error)
			break;
		error=tmp;
	}
	currentIndex=position-1;
	updateCarriageCoor(clearSelect);
}
void FlatText::updateCarriageCoor(BOOL clearSelect)
{
	if(!font||!rect.width)
		return;
	if(currentIndex<0)
		currentIndex=0;
	else if(currentIndex>text.length())
		currentIndex=text.length();
	currentCoorX=font->getTextWidth(text,0,currentIndex);
	int slideChars=__int(slide*rect.width/font->textMetrix.tmAveCharWidth);
	
	if(currentCoorX-slideCoorX>rect.width-margin.x)
		slideCoorX=currentCoorX-rect.width+slideChars*font->textMetrix.tmAveCharWidth;
	else if(currentCoorX+margin.x<slideCoorX)
		slideCoorX=currentCoorX-slideChars*font->textMetrix.tmAveCharWidth;
	if(slideCoorX<0)
		slideCoorX=0;

	updateSelection(clearSelect);
}
void FlatText::updateSelection(BOOL clear)
{
	if(clear)
	{
		selectStartI=currentIndex;
		selectLeft=selectWidth=0;
	}
	else
	{
		float	start=getIndexCoor(selectStartI),
				end=getIndexCoor(currentIndex);
		selectLeft=min(start,end);
		selectWidth=ABS(end-start);
	}
}
void FlatText::applyTextChages(BOOL callback)
{
	updateCarriageCoor();
	if(callback)
		call(FLAT_ACTION_EDIT);
}
void FlatText::copy(BOOL cut)
{
	if(OpenClipboard(window->hWnd))
	{
		if(EmptyClipboard())
		{
			int length=selectLength();
			if(length)
			{
				HANDLE cb=GlobalAlloc(GMEM_MOVEABLE,(length+1)*sizeof(_TCHAR)); // todo: do i have to free this?
				if(cb)
				{
					STR str=(STR)GlobalLock(cb);
					if(str)
					{
						int index=min(selectStartI,currentIndex);
						text.substr(str,index,length);

						if(cut)
						{
							text.remove(index,length);
							selectStartI=currentIndex=index;
							applyTextChages(TRUE);
						}
					}
					GlobalUnlock(cb);
					if(!SetClipboardData(CF_TEXT, cb))
					{
						DWORD error=GetLastError();
						CSTR errstr=getLastErrorStr(error);
						PRINT("Clipboard error: %s\n", errstr);
						GlobalFree(cb);
					}
				}
			}
		}
		CloseClipboard();
	}
}
void FlatText::paste()
{
	if(OpenClipboard(window->hWnd))
	{
		HANDLE cb=GetClipboardData(CF_TEXT);
		if(cb)
        { 
            STR str = (STR)GlobalLock(cb);
			if(str)
			{
				clearSelected();
				currentIndex+=text.insert(str,currentIndex);
				applyTextChages(TRUE);
			}
			GlobalUnlock(cb);
		}
		CloseClipboard();
	}
}
BOOL FlatText::clearSelected()
{
	int len=selectLength();
	if(!len)
		return FALSE;
	int index=min(selectStartI,currentIndex);
	text.remove(index,len);
	selectStartI=currentIndex=index;
	return TRUE;
}
BOOL FlatText::input(BYTE, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	switch(msg)
	{
		case WM_LBUTTONDBLCLK:
		{
			selectAll();
		}
		break;
		case WM_LBUTTONDOWN:
		{
			//if(overed)
				setCarriageCoor(mouse.x-rect.x,!keyboard.keys[VK_SHIFT]);
		}
		break;
		case WM_LBUTTONUP:
		case WM_MOUSEMOVE:
		{
			if(mouse.lbutton)
				setCarriageCoor(mouse.x-rect.x,FALSE);
		}
		break;
		case WM_KEYDOWN:
		{
			waitChar=FALSE;
			if(keyboard.keys[VK_CONTROL])
			{
				switch(keyboard.code)
				{
				case 'V':
					paste();
					break;
				case 'C':
					copy();
					break;
				case 'X':
					copy(TRUE);
					break;
				case 'A':
					selectAll();
					break;
				default:
					return FALSE;
				}
			}
			else switch(keyboard.code)
			{
				case VK_BACK:
				{
					if(!clearSelected())
					{
						text.remove(currentIndex-1);
						currentIndex--;
					}
					applyTextChages(TRUE);
				}
				break;
				case VK_DELETE:
				{
					if(!clearSelected())
						text.remove(currentIndex);
					applyTextChages(TRUE);
				}
				break;
				case VK_LEFT:
				{
					currentIndex--;
					updateCarriageCoor(!keyboard.keys[VK_SHIFT]);
				}
				break;
				case VK_RIGHT:
				{
					currentIndex++;
					updateCarriageCoor(!keyboard.keys[VK_SHIFT]);
				}
				break;
				case VK_END:
				{
					currentIndex=text.length();
					updateCarriageCoor(!keyboard.keys[VK_SHIFT]);
				}
				break;
				case VK_HOME:
				{
					currentIndex=0;
					updateCarriageCoor(!keyboard.keys[VK_SHIFT]);
				}
				break;
				case VK_RETURN:
				{
					call(FLAT_ACTION_DEFAULT);
				}
				break;
				case VK_ESCAPE:
				case VK_TAB:
				case VK_SHIFT:
				{
					return FALSE;
				}
				break;
				default:
				{
					waitChar=TRUE;
				}
			}
			break;
		}
		case WM_CHAR:
		{
			if(waitChar)
			{
				clearSelected();
				text.insert(keyboard.symbol,currentIndex);
				currentIndex++;
				applyTextChages(TRUE);
				waitChar=FALSE;
			}
		}
		break;
		default:
		{
			return FALSE;
		}
	}
	return TRUE;
}
BOOL FlatLabel::resize()
{
	if(!font||!font->loaded())
		return FALSE;
	if(adjustSize)
		size.x=size.y=0;
	if(!size.x)
		size.x=font->getTextWidth(text)+2*margin.x;
	if(!size.y)
		size.y=font->textMetrix.tmHeight+2*margin.y;
	return TRUE;
}
void FlatLabel::draw()
{
	if(font&&text.length())
	{
		if(fontColor)
			font->color=fontColor;
		float w=font->getTextWidth(text),dw=margin.x,dh=margin.y;
		switch(alignx)
		{
		case DIR_NONE:
			dw=0.5f*(rect.width-w);
			break;
		case DIR_RIGHT:
			dw=rect.width-w-margin.x;
			break;
		}
		switch(aligny)
		{
		case DIR_NONE:
			dh=0.5f*(rect.height);
			break;
		case DIR_DOWN:
			dh=rect.height-abs(font->height)-margin.y;
			break;
		}
		font->x=rect.x+dw;
		font->y=rect.y+dh+font->textMetrix.tmDescent;
		font->print(text);
	}
}

Channel::Channel():	channel_x(NULL),
					color(0),thick(1.0f),symOk(FALSE),
					symOffset(0.0f,0.0f),symIndex(-1),
					source(NULL),readable(FALSE),hidden(FALSE),
					stipple_repeat(1),stipple_pattern(0xFFFF),
					data_index(0)
{
	clear();
}
BOOL Channel::findLimits(BOOL toDataIndex)
{
	return limits.add(elems,toDataIndex?data_index:size,TRUE);
}
void Channel::clear()
{
	Vector<double>::clear();
	data_index=size;
	limits.clear();
}
void Channel::addValue(const double &value)
{
	if(size)
	{
		if(graph&&graph->shift)
		{
			shift();
			elems[size-1]=value;
		}
		else
		{
			if(data_index>=size)
				data_index=0;
			elems[data_index++]=value;
		}
	}
}
double Channel::getValue(int index)
{
	if(index>=0&&index<size)
		return elems[index];
	else if(data_index<size)
		return elems[data_index];
	return 0;
}
BOOL Channel::setSource(Vector<double> &newSource)
{
	return setSource(newSource.elems,-newSource.size);
}
BOOL Channel::setSource(double *newSource, int newSize)
{
	if(!newSize)
		return FALSE;
	if(newSize>0)
	{
		if(!setSize(newSize,TRUE))
			return FALSE;
		data_index=newSize;
		source=newSource;
	}
	else
	{
		if(!bind(newSource,-newSize))
			return FALSE;
		data_index=-newSize;
		source=NULL;
	}
	return TRUE;
}

FlatPlotter::FlatPlotter():
	borderLine(1.0f),gridLine(1.0f),markLine(1.0f),
	scale(1,1),
	showMark(TRUE),
	margin(0),
	channelID(0),
	font(NULL),
	extend(TRUE),
	shift(FALSE),
	markIndex(-1),marked(NULL),
	gridColor(0xffA0D0A0),
	markColor(0xff000000),
	fillBlend(0x64ffffff),
	chnlBgColor(INT_WHITE),
	smooth(TRUE),skip(TRUE),
	relative(FALSE),draw_to_index(FALSE),
	logscaleX(FALSE),logscaleY(FALSE),
	symAuto(FALSE),symShow(TRUE),symBox(TRUE),
	commonX(NULL)
{
	setCursor(IDC_HAND);
	formatX.precision=3;
	formatX.sign=' ';
	formatY.copy(formatX);
}
void FlatPlotter::copy(const FlatPlotter &model)
{
	FlatComponent::copy(model);

	grid.copy(model.grid);
	
	borderLine=model.borderLine;
	gridLine=model.gridLine;
	margin=model.margin;
	margin=model.margin;
	marginChannel=model.marginChannel;

	gridColor=model.gridColor;
	chnlBgColor=model.chnlBgColor;
	markColor=model.markColor;

	smooth=model.smooth;
	font=model.font;
	extend=model.extend;
	shift=model.shift;
	skip=model.skip;
	draw_to_index=model.draw_to_index;

	formatX=model.formatX;
	formatY=model.formatY;
}
FlatPlotter::~FlatPlotter()
{
	remChannels();
}
void FlatPlotter::clear(BOOL all)
{
	Lock lock(&cs);
	ChnlObj obj;

	for(obj=channels.first;obj;obj=obj->next)
		if(all||obj->value.source)
			obj->value.clear();
}
void FlatPlotter::remChannels()
{
	Lock lock(&cs);
	channels.destroy();
	commonX=NULL;
	markIndex=-1;
	setMarked();
}
BOOL FlatPlotter::remChannel(const Channel *channel)
{
	if(channel)
	{
		Lock lock(&cs);
		ListObject<Channel> *obj;
		BOOL removed=FALSE;
		for(obj=channels.first;obj;obj=obj->next)
		{
			if(&obj->value==channel)
			{
				channels.remove(obj);
				removed=TRUE;
				break;
			}
		}
		if(removed)
		{
			commonX=NULL;
			markIndex=-1;
			setMarked();
			return TRUE;
		}
	}
	return FALSE;
}
Channel * FlatPlotter::addChannel(	CSTR name, CSTR arg,
									DWORD color, int size, double *data,
									float thick, BOOL hidden,
									BOOL readable)
{
	ChnlObj obj;
	Channel *channel;
	Lock lock(&cs);

	if(!name||!name[0]||getChannelByName(name)||!(obj=channels.insert()))
		return NULL;
	channel=&obj->value;
	channel->arg=arg;
	channel->id=channelID++;
	channel->name=name;
	channel->thick=thick;
	channel->hidden=hidden;
	channel->readable=readable;
	channel->color=color;
	channel->graph=this;
	channel->setSource(data,size);
	return channel;
}
void FlatPlotter::resolveNames(BOOL update)
{
	ListObject<Channel> *obj;
	Channel *chnl,*channel_x;
	BOOL searchX=TRUE;
	commonX=NULL;
	Lock lock(&cs);
	for(obj=channels.first;obj;obj=obj->next)
	{
		chnl=&obj->value;
		CSTR arg=chnl->arg;
		channel_x=chnl->channel_x;
		if((!channel_x||update)&&arg&&arg[0])
		{
			channel_x=getChannelByName(arg);
			chnl->channel_x=channel_x;
		}
		if(searchX&&channel_x&&channel_x!=commonX&&channel_x->size)
		{
			if(commonX)
			{
				commonX=NULL;
				searchX=FALSE;
			}
			else
			{
				commonX=channel_x;
			}
		}
	}
}
BOOL FlatPlotter::setXChannel(CSTR xChannelName, CSTR yChannelName)
{
	Channel *channel_x=getChannelByName(xChannelName);
	if(!channel_x)
		return FALSE;
	if(yChannelName)
	{
		Channel *yChannel=getChannelByName(yChannelName);
		if(!yChannel)
			return FALSE;
		yChannel->channel_x=channel_x;
	}
	else
	{
		ChnlObj obj;
		for(obj=channels.first;obj;obj=obj->next)
			obj->value.channel_x=channel_x;
	}
	channel_x->channel_x=NULL;
	update();
	return TRUE;
}
BOOL FlatPlotter::setSize(int size, CSTR channelName)
{
	BOOL result=TRUE;
	Lock lock(&cs);

	if(channelName)
	{
		Channel *channel=getChannelByName(channelName);
		if(channel&&!channel->setSize(size))
			result=FALSE;
	}
	else
	{
		ChnlObj obj;
		for(obj=channels.first;obj;obj=obj->next)
			if(!obj->value.setSize(size))
			{
				result=FALSE;
				break;
			}
	}
	return result;
}
void FlatPlotter::addValues()
{
	for(ChnlObj obj=channels.first;obj;obj=obj->next)
		obj->value.addValue();
}
void FlatPlotter::addValues(const double * values)
{
	int i;
	ChnlObj obj;
	Lock lock(&cs);
	for(i=0,obj=channels.first;obj;i++,obj=obj->next)
		obj->value.addValue(values[i]);
}
void FlatPlotter::getValues(double * values, int index)
{
	int i;
	ChnlObj obj;
	Lock lock(&cs);
	for(i=0,obj=channels.first;obj;i++,obj=obj->next)
		values[i]=obj->value.getValue(index);
}
Channel * FlatPlotter::getChannelByName(CSTR name)
{
	ChnlObj obj;
	Lock lock(&cs);
	if(name)
		for(obj=channels.first;obj;obj=obj->next)
			if(obj->value.name==name)
				return &obj->value;
	return NULL;
}
Channel* FlatPlotter::getChannelByPoint(int mouseX, int mouseY, int *index)
{
	BOOL found,unigraph;
	int i,points,_index,tmp;
	float x,y;
	double minError=0,error,tmpMinError,*dataX,*dataY,kx,ky;
	ChnlObj obj;
	Channel *channel, *channel_x, *yChannel;
	Lock lock(&cs);

	if(	!chnLimY.span()||
		!chnLimX.span()||
		!drawRect.width)
		return NULL;

	kx=INV(chnLimX.span());
	ky=INV(chnLimY.span());

	if(logscaleX)
		x=(float)logdemap(mouseX,
					drawRect.x,drawRect.x+drawRect.width,
					chnLimX.minValue,chnLimX.maxValue);
	else
		x=(float)remap(mouseX,
					drawRect.x,drawRect.x+drawRect.width,
					chnLimX.minValue,chnLimX.maxValue);
	if(logscaleY)
		y=(float)logdemap(mouseY,
					drawRect.y+drawRect.height,drawRect.y,
					chnLimY.minValue,chnLimY.maxValue);
	else
		y=(float)remap(mouseY,
					drawRect.y+drawRect.height,drawRect.y,
					chnLimY.minValue,chnLimY.maxValue);

	found=FALSE;
	unigraph=FALSE;
	channel_x=NULL;
	yChannel=NULL;
	_index=-1;
	
	if(commonX)
	{
		points=commonX->size;
		dataX=commonX->elems;
		minError=pow2(dataX[0]-x);
		_index=0;
		for(i=1;i<points;i++)
		{
			error=pow2(dataX[i]-x);
			if(minError>error)
			{
				minError=error;
				_index=i;
			}
		}
		for(obj=channels.first;obj;obj=obj->next)
		{
			channel=&obj->value;
			if(	channel->channel_x!=commonX||
				_index>=channel->size||
				!channel->visible())
				continue;
			error=pow2(channel->elems[_index]-y);
			if(!found||minError>error)
			{
				found=TRUE;
				minError=error;
				yChannel=channel;
			}
		}
	}
	else
	{
		for(obj=channels.first;obj;obj=obj->next)
		{
			channel=&obj->value;
			channel_x=channel->channel_x;
			if(	!channel_x||
				!channel->visible()||
				!(points=min(channel->size,channel_x->size)))
				continue;
			dataX=channel_x->elems;
			dataY=channel->elems;
			tmpMinError=pow2(ky*(dataY[0]-y))+pow2(kx*(dataX[0]-x));
			tmp=0;
			for(i=1;i<points;i++)
			{
				error=pow2(ky*(dataY[i]-y))+pow2(kx*(dataX[i]-x));
				if(tmpMinError>error)
				{
					tmpMinError=error;
					tmp=i;
				}
			}
			if(!found||minError>tmpMinError)
			{
				found=TRUE;
				minError=tmpMinError;
				_index=tmp;
				yChannel=channel;
			}
		}
	}
	if(index)
		*index=_index;
	return yChannel;
}
void FlatPlotter::update()
{
	ChnlObj obj,tmp;
	Channel *channel;
	chnLimX.clear();
	chnLimY.clear();
	resolveNames();
	Lock lock(&cs);
	if(limX.span())
	{
		chnLimX.copy(limX);
	}
	else
	{
		for(obj=channels.first;obj;obj=obj->next)
		{
			channel=&obj->value;
			for(tmp=channels.first;tmp;tmp=tmp->next)
			{
				if(tmp->value.channel_x==channel&&tmp->value.visible())
				{
					if(channel->findLimits(draw_to_index))
						chnLimX.add(channel->limits);
					break;
				}
			}
		}
	}
	if(limY.span())
	{
		chnLimY.copy(limY);
	}
	else
	{
		for(obj=channels.first;obj;obj=obj->next)
		{
			channel=&obj->value;
			if(!channel->channel_x||!channel->visible()||!channel->findLimits(draw_to_index))
				continue;
			chnLimY.add(channel->limits);
		}
	}
	if(!chnLimY.span())
		chnLimY.change(-0.5*chnLimX.span(),0.5*chnLimX.span());
	call(FLAT_ACTION_EDIT);
}
void FlatPlotter::drawVertices(GLenum mode, const double *dataX, const double *dataY, int points, double skipStep, BOOL showLevels)
{
	float x,y;
	Lock lock(&cs);

	glBegin(mode);

	remapPoint(dataX[0],dataY[0],&x,&y);
	glVertex2f(x,y);
	if(showLevels)
		glVertex2f(x,drawRect.height);
	if(skipStep<1)
	{
		float skipSum=0,acumX=0,acumY=0,meanX,meanY,rcp;
		DWORD acum=0;
		for(int i=1;i<points-1;i++)
		{
			if(!processing)break;
			if(skipSum<1)
			{
				skipSum+=skipStep;
				acumX+=(float)dataX[i];
				acumY+=(float)dataY[i];
				acum++;
				continue;
			}
			rcp=1.0f/acum;
			meanX=acumX*rcp;
			meanY=acumY*rcp;
			acum=0;
			acumX=acumY=skipSum=0;
			remapPoint(meanX,meanY,&x,&y);
			glVertex2f(x,y);
			if(showLevels)
				glVertex2f(x,drawRect.height);
		}
	}
	else
	{
		for(int i=1;i<points-1;i++)
		{
			if(!processing)break;
			remapPoint(dataX[i],dataY[i],&x,&y);
			glVertex2f(x,y);
			if(showLevels)
				glVertex2f(x,drawRect.height);
		}
	}
	if(processing)
	{
		remapPoint(dataX[points-1],dataY[points-1],&x,&y);
		glVertex2f(x,y);
		if(showLevels)
			glVertex2f(x,drawRect.height);
	}
	glEnd();
}
void FlatPlotter::draw()
{
	int points,i,autosymid=0;
	DWORD color;
	float x,y,thick,value,skipStep;
	double *dataX,*dataY;
	CSTR sym,str;
	ChnlObj obj;
	Channel *channel, *channel_x;

	if(drawRect.width<=0||drawRect.height<=0)
		return;

	Lock lock(&cs);
	processing=TRUE;
	glPushMatrix();
	glTranslated(rect.x,rect.y,0);
	if(bgColor)
		fillBox(0,0,rect.width,rect.height,bgColor);
	if(borderColor&&borderLine)// Frame
	{
		drawBox(0,0,rect.width,rect.height,borderColor,borderLine);
		drawBox(drawRect.x-rect.x,drawRect.y-rect.y,drawRect.width,drawRect.height,borderColor,borderLine);
	}
	
	if(chnLimX.minValue!=chnLimX.maxValue)
	{
		glPushMatrix();
		glTranslated(drawRect.x-rect.x,drawRect.y-rect.y,0);
		//GRID X
		if(font)
		{
			font->y=drawRect.height+font->textMetrix.tmHeight;
			font->align=Font::ALIGN_CENTER;
			font->color=borderColor;
		}
		if(gridColor&&gridLine&&grid.x>=0)
		{
			setColor(gridColor);
			glLineWidth(gridLine);
			for(i=0;i<grid.x;i++)
			{
				x=(i+1)*drawRect.width/(grid.x+1);
				drawLine2(x,0,x,drawRect.height);
				if(font)
				{
					if(logscaleX)
						value=logdemap(	x,
										0.0f,drawRect.width,
										chnLimX.minValue,chnLimX.maxValue);
					else
						value=remap(x,
									0.0f,drawRect.width,
									chnLimX.minValue,chnLimX.maxValue);
					str=formatX.toString(value*scale.x);
					font->x=x;
					font->print(str);
				}
			}
		}
		if(font)
		{
			str=formatX.toString(chnLimX.maxValue*scale.x);
			font->x=drawRect.width;
			font->print(str);
			str=formatX.toString(chnLimX.minValue*scale.x);
			font->x=0;
			font->print(str);
			font->align=Font::ALIGN_RIGHT;
			font->x=-margin;
		}
		//GRID Y
		if(gridColor&&gridLine&&grid.y>=0)
		{
			for(i=0;i<grid.y;i++)
			{
				y=(i+1)*drawRect.height/(grid.y+1);
				drawLine2(0,y,drawRect.width,y);
				if(font)
				{						
					if(logscaleY)
						value=logdemap(	y,
										drawRect.height,0.0f,
										chnLimY.minValue,chnLimY.maxValue);
					else
						value=remap(y,
									drawRect.height,0.0f,
									chnLimY.minValue,chnLimY.maxValue);
					str=formatY.toString(value*scale.y);
					font->y=y+font->textMetrix.tmDescent;
					font->print(str);
				}
			}
		}
		if(font)
		{					
			str=formatY.toString(chnLimY.minValue*scale.y);
			font->y=drawRect.height+font->textMetrix.tmDescent;
			font->print(str);					
			str=formatY.toString(chnLimY.maxValue*scale.y);
			font->y=(float)font->textMetrix.tmDescent;
			font->print(str);
			//font->y=font->textMetrix.tmHeight-margin;
			//dstr=chnLimY.maxValue;
			//font->print(dstr);
			font->align=Font::ALIGN_LEFT;
		}
		// Graphs
		if(channels.length)
		{
			glPushAttrib(GL_ENABLE_BIT);
			glPushAttrib(GL_SCISSOR_BIT);
			glEnable(GL_SCISSOR_TEST);
			glScissor(	__int(drawRect.x),
						__int(window->height-drawRect.y-drawRect.height),
						__int(drawRect.width+1),
						__int(drawRect.height+1));
			if(smooth)
			{
				setBlend(TRUE);
				setSmooth(TRUE,GL_NICEST);
			}
			for(obj=channels.first;obj;obj=obj->next)
			{
				if(!processing)
					break;
				channel=&obj->value;
				channel_x=channel->channel_x;
				if(	!channel_x||
					!channel->visible()||
					!(points=min(channel_x->size,draw_to_index?channel->data_index:channel->size)))
				{
					channel->symOk=FALSE;
					continue;
				}
				glPushAttrib(GL_LINE_BIT);
				dataX=channel_x->elems;
				dataY=channel->elems;
				skipStep=skip?drawRect.width/points:1.0f;

				color=getColor(channel);
				thick=channel->thick;

				if(thick<0)
				{
					setColor(color);
					glPointSize(-thick);
					drawVertices(GL_POINTS,dataX,dataY,points,skipStep);
				}
				else
				{
					if(!thick)
					{
						setColor(color&fillBlend);
						drawVertices(GL_QUAD_STRIP,dataX,dataY,points,skipStep,TRUE);
					}
					else
					{
						glLineWidth(thick);
						setLineStipple(	channel->stipple_pattern!=0xFFFF,
										channel->stipple_pattern,
										channel->stipple_repeat);
					}
					setColor(color);
					drawVertices(GL_LINE_STRIP,dataX,dataY,points,skipStep);
				}
				channel->symOk=font&&symShow&&(symAuto||channel->sym.length());
				if(processing&&channel->symOk)
				{
					if(channel->symIndex>=0&&channel->symIndex<=100)
						i=round(channel->symIndex*points*0.01);
					else
						i=points>>1;
					remapPoint(dataX[i],dataY[i],&channel->symPos.x,&channel->symPos.y);
				}
				glPopAttrib(); // GL_LINE_BIT
			}
			glPopAttrib(); // GL_SCISSOR_BIT
			glPopAttrib(); // GL_ENABLE_BIT
			for(obj=channels.first;obj;obj=obj->next)
			{
				if(!processing)
					break;
				channel=&obj->value;
				if(!channel->symOk)
					continue;
				if(channel->sym.length())
					sym=channel->sym;
				else
					sym=format.toString(++autosymid);
				float symWidth=font->getTextWidth(sym);
				font->x=channel->symPos.x+channel->symOffset.x-0.5f*symWidth;
				font->y=channel->symPos.y+channel->symOffset.y;
				color=getColor(channel);
				if(symBox)
				{
					float	boxy=font->y+font->textMetrix.tmDescent-font->textMetrix.tmHeight,
							boxx=font->x-0.5f*font->textMetrix.tmAveCharWidth,
							boxw=symWidth+font->textMetrix.tmAveCharWidth,
							boxh=float(font->textMetrix.tmHeight+2);
					if(channel->symOffset.x||channel->symOffset.y)
						drawLine2(
							channel->symPos.x+channel->symOffset.x,
							channel->symPos.y+channel->symOffset.y,
							channel->symPos.x,
							channel->symPos.y,
							color);
					if(bgColor)
						fillBox(
							boxx,boxy,
							boxw,boxh,
							bgColor);
					drawBox(boxx,boxy,
							boxw,boxh,
							color);
				}
				font->color=color;
				font->print(sym);
			}
			if(	processing&&
				showMark&&markColor&&
				marked&&marked->channel_x&&
				markIndex>=0&&markIndex<min(marked->size,marked->channel_x->size))
			{
				dataX=marked->channel_x->elems;
				dataY=marked->elems;
				remapPoint(dataX[markIndex],dataY[markIndex],&x,&y);
				glLineWidth(markLine);
				setColor(markColor);
				glBegin(GL_LINES);
					glVertex2f( x, 0);
					glVertex2f( x, drawRect.height);
					glVertex2f( 0, y);
					glVertex2f( drawRect.width, y);
				glEnd();
				font->color=0;
				str=formatY.toString(scale.y*dataY[markIndex]);
				font->y=y-font->textMetrix.tmDescent;
				font->x=0;
				font->print(str);
				str=formatX.toString(scale.x*dataX[markIndex]);
				font->y=drawRect.height-font->textMetrix.tmDescent;
				font->x=x;
				font->print(str);
			}
		}
		glPopMatrix();
	}
	glPopMatrix();
	processing=FALSE;
}
void FlatPlotter::build()
{
	drawRect.x=rect.x+margin+marginChannel.y;
	drawRect.y=rect.y+margin;
	drawRect.width=rect.width-2*margin-1.5f*marginChannel.y;
	drawRect.height=rect.height-2*margin-marginChannel.x;
	resolveNames();
	FlatComponent::build();
}
int FlatPlotter::load(FILE * file, BOOL rem, BOOL text, BOOL sampling, CSTR term)
{
	if(file)
	{
		Lock lock(&cs);
		if(rem)
			remChannels();
		if(text)
		{
			int c,chars,samples;
			double value;
			BOOL reading=TRUE;
			_TCHAR str[256];
			CSTR xChannelName;
			ListObject<Channel> *obj;
			Channel *channel;

			samples=-1;
			while(reading)
			{
				switch(c=_fgettc(file))
				{
					case _TEOF:
					{
						reading=FALSE;
					}
					break;
					case 0x0A:
					{
						samples++;
					}
					break;
				}
			}
			if(samples<=0)
				return 0;
			xChannelName=NULL;
			if(sampling)
			{
				if(!(channel=addChannel(_T("Sample"),xChannelName,0,samples)))
					return 0;
				xChannelName=channel->name;
				channel->color=INT_WHITE;
				channel->hidden=TRUE;
			}
			chars=0;
			fseek(file,0,SEEK_SET);
			reading=TRUE;
			while(reading)
			{
				switch(c=_fgettc(file))
				{
					case _TEOF:
					{
						return 0;
					}
					case 0x0A:
					{
						reading=FALSE;
					}
					break;
					case 0x20:
					{
						str[chars]=0;
						if(!(channel=addChannel(str,xChannelName,0,samples)))
							return 0;
						if(!xChannelName)
							xChannelName=channel->name;
						chars=0;
					}
					break;
					default:
					{
						str[chars]=(_TCHAR)c;
						chars++;
					}
				}
			}
			samples=0;
			obj=channels.first;
			reading=TRUE;
			while(reading)
			{
				switch(c=_fgettc(file))
				{
					case _TEOF:
					{
						reading=FALSE;
					}
					break;
					case 0x0A:
					{
						obj=channels.first;
						if(sampling)
						{
							obj->value.addValue(samples++);
							obj=obj->next;
						}
					}
					break;
					case 0x20:
					{
						str[chars]=0;
						if(obj)
						{
							_stscanf(str,_T("%lf"),&value);
							obj->value.addValue(value);
							obj=obj->next;
						}
						chars=0;
					}
					break;
					default:
					{
						str[chars]=(_TCHAR)c;
						chars++;
					}
				}
			}
		}
		else
		{
			DWORD head=0;
			int num;
			Channel *chnl=NULL;

			String name,xName,symbol;
			DWORD color;
			int sym_index=-1,size;
			float thickness;
			BOOL hidden;
			GLushort stipple_pattern;
			GLint stipple_repeat;
			String tmp1,tmp2;

			if(fread(&head,4,1,file)&&head==PLOT_HEADER)
			{
				while(fread(&head,4,1,file)&&head==CHNL_HEADER)
				{
					if(!fread(&num,4,1,file)||num<=0||!name.setSize(num)||!name.read(file))
						return 0;
					if(!fread(&num,4,1,file))
						return 0;
					if(num>0)
					{
						if(!xName.setSize(num)||!xName.read(file))
							return 0;
					}
					else
					{
						xName.destroy();
					}
					if(	!fread(&num,4,1,file))
						return 0;
					if(num>0)
					{
						if(!symbol.setSize(num)||symbol.read(file)||!fread(&sym_index,4,1,file))
							return 0;
					}
					else
					{
						symbol.destroy();
						sym_index=-1;
					}
					if(	!fread(&color,4,1,file)||
						!fread(&thickness,4,1,file)||
						!fread(&stipple_pattern,2,1,file)||
						!fread(&stipple_repeat,4,1,file)||
						!fread(&hidden,4,1,file)||
						!fread(&size,4,1,file))
						return 0;
					if(term)
					{
						name+=term;
						xName+=term;
					}
					chnl=addChannel(name,xName,color,size,NULL,thickness,hidden);
					if(!chnl||(int)fread(chnl->elems,sizeof(chnl->elems[0]),size,file)!=size)
						return 0;
					chnl->setStipple(stipple_pattern,stipple_repeat);
					chnl->setSymbol(symbol,sym_index);
				}
			}
		}
		update();
		return channels.length;
	}
	return 0;
}
BOOL FlatPlotter::save(FILE * file, BOOL readonly, CSTR term)
{
	if(file)
	{
		Lock lock(&cs);
		DWORD head=PLOT_HEADER;
		int num,termNum=strLen(term);
		ChnlObj obj;
		Channel *chnl;
		String str;
		fwrite(&head,4,1,file);
		head=CHNL_HEADER;
		for(obj=channels.first;obj;obj=obj->next)
		{
			chnl=&obj->value;
			if(readonly&&!chnl->readable&&chnl->channel_x!=NULL)
				continue;
			fwrite(&head,4,1,file);
			num=chnl->name.length()+termNum;
			fwrite(&num,4,1,file);
			str=chnl->name;
			if(termNum)
				str+=term;
			fwrite((CSTR)str,sizeof(_TCHAR),num,file);
			num=chnl->channel_x?chnl->channel_x->name.length():0;
			if(num>0)
			{
				num+=termNum;
				fwrite(&num,4,1,file);
				str=chnl->channel_x->name;
				if(termNum)
					str+=term;
				fwrite((CSTR)str,sizeof(_TCHAR),num,file);
			}
			else
			{
				fwrite(&num,4,1,file);
			}
			num=chnl->sym.length();
			if(num>0)
			{
				num+=termNum;
				fwrite(&num,4,1,file);
				str=chnl->sym;
				if(termNum)
					str+=term;
				fwrite((CSTR)str,sizeof(_TCHAR),num,file);
				fwrite(&chnl->symIndex,4,1,file);
			}
			else
			{
				fwrite(&num,4,1,file);
			}
			fwrite(&chnl->color,4,1,file);
			fwrite(&chnl->thick,4,1,file);
			fwrite(&chnl->stipple_pattern,2,1,file);
			fwrite(&chnl->stipple_repeat,4,1,file);
			fwrite(&chnl->hidden,4,1,file);
			fwrite(&chnl->size,4,1,file);
			fwrite(chnl->elems,sizeof(chnl->elems[0]),chnl->size,file);
		}
		return TRUE;
	}
	return FALSE;
}
BOOL FlatPlotter::input(BYTE type, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	if(FlatComponent::input(type,msg,mouse,keyboard))
		return TRUE;
	Lock lock(&cs);
	ChnlObj obj;
	Channel *channel,*old;
	int index,length;
	switch(type)
	{
	case MOUSE_INPUT_MSG:
		if(mouse.rbutton||mouse.lbutton)
		{
			if(drawRect.contain((float)mouse.x,(float)mouse.y))
			{
				old=marked;
				if(mouse.rbutton)
				{
					if(msg==WM_RBUTTONDOWN)
					{
						marked=NULL;
						markIndex=-1;
					}
				}
				else
				{
					channel=getChannelByPoint(mouse.x,mouse.y,&index);
					if(channel&&(length=channel->getSize()))
					{
						marked=channel;
						markIndex=index;
					}
					else
					{
						marked=NULL;
						markIndex=-1;
					}
				}
				if(old||marked)
					call(FLAT_ACTION_SELECT,marked);
				return TRUE;
			}
		}
		else if(msg==WM_MOUSEWHEEL)
		{
			/*
			if(mouse.wheel>0)
				scroll.decrease();
			else
				scroll.increase();
			return TRUE;
			*/
		}
		break;
	case KEY_INPUT_MSG:
		if(keyboard.waiting)
		{
			if(keyboard.keys[VK_DELETE])
			{
				hideAll();
				return TRUE;
			}
			else if(keyboard.keys[VK_F7])
			{
				for(obj=channels.first;obj;obj=obj->next)
					obj->value.symIndex=(char)__int(random(1,obj->value.size-1));
				return TRUE;
			}
			else if(keyboard.keys[VK_F8])
			{
				symShow=!symShow;
				return TRUE;
			}
			else if(keyboard.keys[VK_RIGHT])
			{
				if(marked&&(length=marked->getSize()))
				{
					if(keyboard.keys[VK_SHIFT]&&length>=200)
						index=markIndex+length/100;
					else
						index=markIndex+1;
					if(index<length)
					{
						markIndex=index;
						call(FLAT_ACTION_SELECT,marked);
					}
					return TRUE;
				}
			}
			else if(keyboard.keys[VK_LEFT])
			{
				if(marked&&(length=marked->getSize()))
				{
					if(keyboard.keys[VK_SHIFT]&&length>=200)
						index=markIndex-length/100;
					else
						index=markIndex-1;
					if(index>=0)
					{
						markIndex=index;
						call(FLAT_ACTION_SELECT,marked);
					}
					return TRUE;
				}
			}
			if(keyboard.keys[VK_UP])
			{
				if(marked)
				{
					for(obj=channels.first;obj;obj=obj->next)
					{
						if(&obj->value==marked)
							break;
					}
					for(obj=obj->next;obj;obj=obj->next)
					{
						if(obj->value.visible())
						{
							setMarked(&obj->value);
							return TRUE;
						}
					}
				}
				return TRUE;
			}
			else if(keyboard.keys[VK_DOWN])
			{
				if(marked)
				{
					for(obj=channels.last;obj;obj=obj->prev)
					{
						if(&obj->value==marked)
							break;
					}
					for(obj=obj->prev;obj;obj=obj->prev)
					{
						if(obj->value.visible())
						{
							setMarked(&obj->value);
							return TRUE;
						}
					}
				}
			}
		}
		break;
	}
	return FALSE;
}
FlatImage::FlatImage():image(NULL),zoom(FALSE)
{
	zoom_x=zoom_y=1.0f;
	isStatic=FALSE;
	setCursor(IDC_HAND);
}
void FlatImage::draw()
{
	glPixelZoom(zoom_x,zoom_y);
	drawImage(image, rect.x, rect.y+rect.height, ALIGN_ADJUST);
	if(borderColor&&borderLine)
		drawBox(rect.x,rect.y,rect.width,rect.height,borderColor,borderLine);
}
BOOL FlatImage::resize()
{
	if(!image)
		return FALSE;
	if(adjustSize)
		size.x=size.y=0;
	if(!size.x)
		size.x=(float)image->width;
	if(!size.y)
		size.y=(float)image->height;
	return TRUE;
}
void FlatImage::build()
{
	if(image&&image->width&&image->height)
	{
		zoom_x=size.x/image->width;
		zoom_y=size.y/image->height;
		zoom=(zoom_x!=1.0f||zoom_y!=1.0f);
	}
	FlatComponent::build();
}
void FlatImage::copy(const FlatImage &model)
{
	FlatComponent::copy(model);
	image=model.image;
}
BOOL FlatImage::input(BYTE type,UINT msg,const Mouse &mouse,const Keyboard &keyboard)
{
	if(FlatComponent::input(type,msg,mouse,keyboard))
		return TRUE;
	switch(type)
	{
	case MOUSE_INPUT_MSG:
		if((mouse.lbutton||msg==WM_LBUTTONUP||mouse.rbutton||msg==WM_RBUTTONUP)&&rect.contain(mouse.x,mouse.y))
		{
			call(FLAT_ACTION_DEFAULT,&mouse);
			return TRUE;
		}
		break;
	case KEY_INPUT_MSG:
		break;
	}
	return FALSE;
}
FlatSelect::FlatSelect():
	readLines(-1),
	optionStart(0)
{
	scroll.colorStart=INT_WGRAY;
	scroll.colorEnd=INT_WGRAY;
	scroll.size.x=10;
	scroll.borderLine=1;
	scroll.setDirection(DIR_DOWN);
	addChild(&scroll);
}
void FlatSelect::copy(const FlatSelect &select)
{
	FlatList::copy(select);
	scroll.copy(select.scroll);
	readLines=select.readLines;
}
BOOL FlatSelect::input(BYTE type, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	if(FlatComponent::input(type,msg,mouse,keyboard))
		return TRUE;
	switch(type)
	{
	case MOUSE_INPUT_MSG:
		if(mouse.lbutton&&options.length)
		{
			double ratio=rect.ratioy((float)mouse.y);
			int index=min(__int(ratio*_readLines),options.length-1);
			OptionObject obj=options.objectAt(optionStart+index);
			if(obj)
				selectIndex=optionStart+index;
			else
				selectIndex=-1;
			if(msg==WM_LBUTTONUP)
				call(FLAT_ACTION_DEFAULT);
			return TRUE;
		}
		else if(msg==WM_MOUSEWHEEL)
		{
			if(mouse.wheel>0)
				scroll.decrease();
			else
				scroll.increase();
			return TRUE;
		}
		break;
	case KEY_INPUT_MSG:
		if(keyboard.keys[VK_UP])
		{
			scroll.decrease();
			selectPrevIndex();
			call(FLAT_ACTION_DEFAULT);
			return TRUE;
		}
		else if(keyboard.keys[VK_DOWN])
		{
			scroll.increase();
			selectNextIndex();
			call(FLAT_ACTION_DEFAULT);
			return TRUE;
		}
		else if(keyboard.keys[VK_DELETE])
		{
			call(FLAT_ACTION_DELETE);
			return TRUE;
		}
		break;
	}
	return FALSE;
}
BOOL FlatSelect::listen(WORD id, WORD code, const void *)
{
	if(id==scroll.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			optionStart=round(optionSpan*scroll.getRatio());
			return TRUE;
		}
	}
	return FALSE;
}
void FlatSelect::draw()
{
	if(bgColor)
		fillBox(rect.x,rect.y,rect.width,rect.height,bgColor);
	float border=borderLine;
	if(activeState)
		border=activeLine;
	DWORD color=borderColor;
	if(disabled)
		color=disabledColor;
	if(color&&border)
		drawBox(rect.x,rect.y,rect.width,rect.height,color,border);
	OptionObject obj;
	float yPos=rect.y;
	int i,j;
	for(	obj=options.objectAt(optionStart),j=0,i=optionStart;
			j<_readLines&&obj;
			obj=obj->next,j++,i++)
	{
		font->x=rect.x+margin.x;
		yPos+=float(font->textMetrix.tmHeight);
		font->y=yPos;
		if(i==selectIndex)
		{
			DWORD oldColor=font->color;
			if(selectBgColor)
				fillBox(rect.x,yPos-font->textMetrix.tmAscent,
						rect.width,float(font->textMetrix.tmHeight),selectBgColor);
			if(selectForColor)
				font->color=selectForColor;
			font->print(obj->value);
			font->color=oldColor;
		}
		else
		{
			font->print(obj->value);
		}
	}
	FlatComponent::draw();
}
BOOL FlatSelect::resize()
{
	if(!font||!font->loaded()||(!options.length&&readLines<0))
		return FALSE;
	if(adjustSize)
		size.x=size.y=0;
	if(!size.x)
	{
		float length,maxLength=0;
		OptionObject obj;
		for(obj=options.first;obj;obj=obj->next)
		{
			length=font->getTextWidth(obj->value);
			if(maxLength<length)
				maxLength=length;
		}
		size.x=maxLength+2*margin.x+scroll.size.x;
	}
	if(!size.y)
	{
		if(readLines<0)
			size.y=float(font->textMetrix.tmHeight*options.length+margin.y);
		else
			size.y=float(font->textMetrix.tmHeight*readLines+margin.y);
	}
	return TRUE;
}
void FlatSelect::build()
{
	if(options.length)
	{
		_readLines=(readLines<0)?options.length:readLines;
		if(font&&font->textMetrix.tmHeight)
			_readLines=minval(_readLines,__int(size.y/font->textMetrix.tmHeight));
		if(_readLines)
		{
			if(optionSpan=options.length-_readLines)
			{
				double tmp=INV(optionSpan);
				scroll.step=tmp;
				scroll.page=10*tmp;
			}
			scroll.visible=TRUE;
			scroll.location.x=size.x-scroll.size.x;
			scroll.location.y=0;
			scroll.size.y=size.y;
		}
		else
		{
			optionSpan=0;
			scroll.visible=FALSE;
		}
		optionStart=round(optionSpan*scroll.getRatio());
	}
	FlatComponent::build();
}
FlatGraph::FlatGraph():
	chnlReadLines(-1),
	channelStart(0),
	chnlBordColor(INT_BLACK),
	chnlBordLine(1),toolmarge(90),
	toolbox(TRUE),chnlinfo(TRUE),sorted(TRUE),
	chnl_str(127)
{
	addChild(&graph);

	bLog.text=_T("Log");
	bLog.borderLine=1;
	bLog.borderColor=INT_BLACK;
	bLog.overColor=INT_WHITE;
	bLog.pressColor=INT_WYELLOW;
	bLog.activeLine=2;
	bLog.fontColor=INT_BLACK;
	bLog.margin.set(10,1);
	bLog.bgColor=0xffdddddd;
	bLog.checkable=TRUE;
	bLog.size.y=20;
	addChild(&bLog,DIR_DOWN,0,10,&graph);

	bSymShow.copy(bLog);
	bSymShow.text=_T("Sym");
	addChild(&bSymShow,DIR_RIGHT,5);

	bSmooth.copy(bLog);
	bSmooth.text=_T("Smooth");
	addChild(&bSmooth,DIR_RIGHT,5);

	bSkip.copy(bLog);
	bSkip.text=_T("Skip");
	addChild(&bSkip,DIR_RIGHT,5);

	bExp.copy(bLog);
	bExp.text=_T("Exp");
	addChild(&bExp,DIR_RIGHT,5);
	
	bSelect.copy(bLog);
	bSelect.text=_T("Sel");
	addChild(&bSelect,DIR_RIGHT,5);

	bChnls.copy(bLog);
	bChnls.checkable=FALSE;
	bChnls.text=_T("Chnl");
	addChild(&bChnls,DIR_RIGHT,5);

	tbChnls.copy(tbMarginX);
	tbChnls.borderLine=1;
	tbChnls.borderColor=INT_BLACK;
	tbChnls.activeLine=2;
	tbChnls.fontColor=INT_BLACK;
	tbChnls.margin.set(2,1);
	tbChnls.size.set(25,20);
	tbChnls.format.precision=3;
	tbChnls.format.exposant=TRUE;
	addChild(&tbChnls,DIR_RIGHT,2);

	bLim.copy(bLog);
	bLim.text=_T("L");
	bLim.size.y=42;
	addChild(&bLim,DIR_DOWN,0,5,&bLog);

	tbMinX.copy(tbChnls);
	tbMinX.size.x=115;
	addChild(&tbMinX,DIR_RIGHT,2);

	tbMaxX.copy(tbMinX);
	addChild(&tbMaxX,DIR_RIGHT,2);

	tbMinY.copy(tbMinX);
	addChild(&tbMinY,DIR_DOWN,0,2,&tbMinX);
	tbMaxY.copy(tbMinX);
	addChild(&tbMaxY,DIR_RIGHT,2);

	bPrec.copy(bChnls);
	bPrec.size.y=42;
	bPrec.text=_T("P");
	addChild(&bPrec,DIR_RIGHT,5,0,&tbMaxX);
	tbPrecX.copy(tbMinX);
	tbPrecX.size.x=25;
	addChild(&tbPrecX,DIR_RIGHT,2);
	tbPrecY.copy(tbPrecX);
	addChild(&tbPrecY,DIR_DOWN,0,2,&tbPrecX);

	bGrid.copy(bPrec);
	bGrid.text=_T("G");
	addChild(&bGrid,DIR_RIGHT,5,0,&tbPrecX);
	tbGridX.copy(tbPrecX);
	addChild(&tbGridX,DIR_RIGHT,2);
	tbGridY.copy(tbGridX);
	addChild(&tbGridY,DIR_DOWN,0,2,&tbGridX);

	bMargin.copy(bPrec);
	bMargin.text=_T("M");
	addChild(&bMargin,DIR_RIGHT,5,0,&tbGridX);
	tbMarginX.copy(tbPrecX);
	tbMarginX.format.precision=0;
	tbMarginX.format.exposant=FALSE;
	tbMarginX.format.sign=0;
	addChild(&tbMarginX,DIR_RIGHT,2);
	tbMarginY.copy(tbMarginX);
	addChild(&tbMarginY,DIR_DOWN,0,2,&tbMarginX);
	/*
	bScale.copy(bPrec);
	bScale.text=_T("S");
	bScale.stick(,DIR_RIGHT,5,0,&tbMarginX);
	addChild(&bScale);
	tbScaleX.copy(tbMarginX);
	tbScaleX.format.precision=2;
	tbScaleX.size.x=45;
	tbScaleX.stick(,DIR_RIGHT,2,0,&bScale);
	addChild(&tbScaleX);
	tbScaleY.copy(tbScaleX);
	tbScaleY.stick(,DIR_DOWN,0,2,&tbScaleX);
	addChild(&tbScaleY);
	*/

	bColor.copy(bPrec);
	bColor.size.y=20;
	bColor.text=_T("Color");
	addChild(&bColor,DIR_DOWN,0,5,&bLim);

	tbColor.copy(tbMarginX);
	tbColor.size.x=65;
	tbColor.format.hexa=TRUE;
	addChild(&tbColor,DIR_RIGHT,2);

	bThick.copy(bColor);
	bThick.text=_T("Thick");
	addChild(&bThick,DIR_RIGHT,5);

	tbThick.copy(tbMarginX);
	tbThick.size.x=25;
	tbThick.format.precision=1;
	addChild(&tbThick,DIR_RIGHT,2);

	bSymbol.copy(bColor);
	bSymbol.text=_T("Sym");
	addChild(&bSymbol,DIR_RIGHT,5);

	tbSymbol.copy(tbColor);
	tbSymbol.size.x=35;
	addChild(&tbSymbol,DIR_RIGHT,2);

	tbSymIndex.copy(tbPrecX);
	addChild(&tbSymIndex,DIR_RIGHT,2);
	
	tbSymOffsetX.copy(tbSymIndex);
	tbSymOffsetX.format.precision=0;
	tbSymOffsetX.format.exposant=FALSE;
	addChild(&tbSymOffsetX,DIR_RIGHT,2);

	tbSymOffsetY.copy(tbSymOffsetX);
	addChild(&tbSymOffsetY,DIR_RIGHT,2);
	
	bStipple.copy(bColor);
	bStipple.text=_T("Stip");
	addChild(&bStipple,DIR_RIGHT,5);

	tbStipplePattern.copy(tbColor);
	tbStipplePattern.size.x=35;
	addChild(&tbStipplePattern,DIR_RIGHT,2);

	tbStippleRepeat.copy(tbPrecX);
	tbStippleRepeat.size.x=20;
	addChild(&tbStippleRepeat,DIR_RIGHT,2);

	scroll.colorStart=INT_WGRAY;
	scroll.colorEnd=INT_WGRAY;
	scroll.size.x=10;
	scroll.borderLine=1;
	scroll.setDirection(DIR_DOWN);
	addChild(&scroll);

	graph.relative=TRUE;
	setCursor(IDC_HAND);
}
void FlatGraph::copy(const FlatGraph &flatGraph)
{
	FlatComponent::copy(flatGraph);
	
	scroll.copy(flatGraph.scroll);
	bLog.copy(flatGraph.bLog);
	bSymShow.copy(flatGraph.bSymShow);
	bSmooth.copy(flatGraph.bSmooth);
	bSkip.copy(flatGraph.bSkip);
	bExp.copy(flatGraph.bExp);
	bColor.copy(flatGraph.bColor);
	tbColor.copy(flatGraph.tbColor);
	bThick.copy(flatGraph.bThick);
	tbThick.copy(flatGraph.tbThick);
	bChnls.copy(flatGraph.bChnls);
	tbChnls.copy(flatGraph.tbChnls);
	bLim.copy(flatGraph.bLim);
	tbMinX.copy(flatGraph.tbMinX);
	tbMaxX.copy(flatGraph.tbMaxX);
	tbMinY.copy(flatGraph.tbMinY);
	tbMaxY.copy(flatGraph.tbMaxY);
	bPrec.copy(flatGraph.bPrec);
	tbPrecX.copy(flatGraph.tbPrecX);
	tbPrecY.copy(flatGraph.tbPrecY);
	bGrid.copy(flatGraph.bGrid);
	tbGridX.copy(flatGraph.tbGridX);
	tbGridY.copy(flatGraph.tbGridY);
	bMargin.copy(flatGraph.bMargin);
	tbMarginX.copy(flatGraph.tbMarginX);
	tbMarginY.copy(flatGraph.tbMarginY);
	bScale.copy(flatGraph.bMargin);
	tbScaleX.copy(flatGraph.tbScaleX);
	tbScaleY.copy(flatGraph.tbScaleY);
	toolbox=flatGraph.toolbox;
	graph.copy(flatGraph.graph);

	chnlReadLines=flatGraph.chnlReadLines;
	chnlBordColor=flatGraph.chnlBordColor;
	chnlBordLine=flatGraph.chnlBordLine;
}
BOOL FlatGraph::input(BYTE type, UINT msg, const Mouse &mouse, const Keyboard &keyboard)
{
	if(FlatComponent::input(type,msg,mouse,keyboard))
		return TRUE;
	ReadChnlObject obj;
	switch(type)
	{
	case MOUSE_INPUT_MSG:
		if(msg==WM_LBUTTONDOWN||msg==WM_RBUTTONDOWN)
		{
			if(chnlRect.contain((float)mouse.x,(float)mouse.y))
			{
				if(readChannels.length)
				{
					double ratio=chnlRect.ratioy((float)mouse.y);
					int index=min(__int(ratio*_channelRead),readChannels.length-1);
					obj=readChannels.objectAt(channelStart+index);
					if(obj)
					{
						Channel *channel=obj->value;
						if(msg==WM_LBUTTONDOWN)
						{
							channel->hidden=!channel->hidden;
							graph.setMarked(channel->hidden?NULL:channel);
							graph.update();
						}
						else
						{
							channel->color=colorRand();
						}
					}
				}
			}
			else
			{
				toggleToolbox();
			}
			return TRUE;
		}
		else if(msg==WM_MOUSEWHEEL)
		{
			if(mouse.wheel>0)
				scroll.decrease();
			else
				scroll.increase();
			return TRUE;
		}
		break;
	case KEY_INPUT_MSG:
		if(keyboard.keys[VK_UP])
		{
			scroll.decrease();
			return TRUE;
		}
		else if(keyboard.keys[VK_DOWN])
		{
			scroll.increase();
			return TRUE;
		}
		else if(keyboard.keys[VK_F4])
		{
			toggleToolbox();
			return TRUE;
		}
		else
		{
			graph.input(type,msg,mouse,keyboard);
		}
		break;
	}
	return FALSE;
}
BOOL FlatGraph::listen(WORD id, WORD code, const void *info)
{
	if(id==scroll.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
			channelStart=round(channelSpan*scroll.getRatio());
	}
	else if(id==bLog.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.logscaleX=bLog.checked;
		}
	}
	else if(id==bSymShow.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.symShow=bSymShow.checked;
		}
	}
	else if(id==bSmooth.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.smooth=bSmooth.checked;
		}
	}
	else if(id==bSkip.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.skip=bSkip.checked;
			graph.update();
		}
	}
	else if(id==bExp.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.formatY.exposant=bExp.checked;
		}
	}
	else if(id==bLim.id||
			id==tbMinY.id||id==tbMaxY.id||
			id==tbMinX.id||id==tbMaxX.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			if(bLim.checked)
			{
				graph.limY.set(tbMinY.getDouble(),tbMaxY.getDouble());
				graph.limX.set(tbMinX.getDouble(),tbMaxX.getDouble());
			}
			else
			{
				graph.limY.clear();
				graph.limX.clear();
			}
			graph.update();
		}
	}
	else if(id==bSelect.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			ReadChnlObject obj;
			for(obj=readChannels.first;obj;obj=obj->next)
				obj->value->hidden=!bSelect.checked;
			graph.update();
		}
	}
	else if(id==bColor.id||id==tbColor.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			ReadChnlObject obj;
			DWORD color=tbColor.getInt();
			if(graph.marked)
			{
				graph.marked->color=color;
			}
			else
			{
				for(obj=readChannels.first;obj;obj=obj->next)
					if(!obj->value->hidden)
						obj->value->color=color;
			}
		}
	}
	else if(id==bStipple.id||id==tbStipplePattern.id||id==tbStippleRepeat.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			ReadChnlObject obj;
			WORD pattern=(WORD)tbStipplePattern.getInt();
			int repeat=tbStippleRepeat.getInt();
			if(graph.marked)
			{
				graph.marked->setStipple(pattern,repeat);
			}
			else
			{
				for(obj=readChannels.first;obj;obj=obj->next)
					if(!obj->value->hidden)
						obj->value->setStipple(pattern,repeat);
			}
		}
	}
	else if(id==bSymbol.id||
			id==tbSymbol.id||
			id==tbSymIndex.id||
			id==tbSymOffsetX.id||
			id==tbSymOffsetY.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			ReadChnlObject obj;
			CSTR symbol=tbSymbol.getText();
			int index=tbSymIndex.getInt();
			float offsetX=(float)tbSymOffsetX.getDouble();
			float offsetY=(float)tbSymOffsetY.getDouble();
			if(graph.marked)
			{
				graph.marked->setSymbol(symbol,index,offsetX,offsetY);
			}
			else
			{
				for(obj=readChannels.first;obj;obj=obj->next)
					if(!obj->value->hidden)
						obj->value->setSymbol(symbol,index,offsetX,offsetY);
			}
		}
	}
	else if(id==bThick.id||id==tbThick.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			ReadChnlObject obj;
			float thick=(float)tbThick.getDouble();
			if(graph.marked)
			{
				graph.marked->thick=thick;
			}
			else
			{
				for(obj=readChannels.first;obj;obj=obj->next)
					if(!obj->value->hidden)
						obj->value->thick=thick;
			}
		}
	}
	else if(id==bChnls.id||id==tbChnls.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			chnlReadLines=tbChnls.getInt();
			build();
		}
	}
	else if(id==bPrec.id||
			id==tbPrecX.id||id==tbPrecY.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.formatX.precision=(char)tbPrecX.getInt();
			graph.formatY.precision=(char)tbPrecY.getInt();
		}
	}
	else if(id==bGrid.id||
			id==tbGridX.id||id==tbGridY.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
			graph.grid.set(tbGridX.getInt(),tbGridY.getInt());
	}
	else if(id==bMargin.id||
			id==tbMarginX.id||id==tbMarginY.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.marginChannel.set((float)tbMarginX.getDouble(),(float)tbMarginY.getDouble());
			graph.build();
		}
	}
	else if(id==bScale.id||
			id==tbScaleX.id||id==tbScaleY.id)
	{
		if(code==FLAT_ACTION_DEFAULT)
		{
			graph.scale.set((float)tbScaleX.getDouble(),(float)tbScaleY.getDouble());
			//graph.update();
		}
	}
	else if(id==graph.id)
	{
		if(code==FLAT_ACTION_EDIT)
		{
			if(!bLim.checked)
			{
				Limit<double> lx,ly;
				graph.getRealLims(lx,ly);
				tbMinY.setDouble(ly.minValue);
				tbMinX.setDouble(lx.minValue);
				tbMaxY.setDouble(ly.maxValue);
				tbMaxX.setDouble(lx.maxValue);
			}
		}
		else if(code==FLAT_ACTION_SELECT)
		{
			showActiveInfo();
			if(info&&channelSpan)
			{
				Channel *marked=(Channel*)info;
				ReadChnlObject obj;
				int j;
				for(obj=readChannels.first,j=0;obj;obj=obj->next,j++)
				{
					if(obj->value==marked)
					{
						if(j<channelStart)
							scroll.setRatio(j*INV(channelSpan),TRUE);
						else if(j>=channelStart+_channelRead)
							scroll.setRatio((j-_channelRead+1)*INV(channelSpan),TRUE);
						break;
					}
				}
			}
		}
		return call(code,info);
	}
	else
	{
		return FALSE;
	}
	return TRUE;
}
void FlatGraph::draw()
{
	if(bgColor)
		fillBox(rect.x,rect.y,rect.width,rect.height,bgColor);
	float border=borderLine;
	if(activeState)
		border=activeLine;
	DWORD color=borderColor;
	if(disabled)
		color=disabledColor;
	if(color&&border)
		drawBox(rect.x,rect.y,rect.width,rect.height,color,border);
	if(chnlRect.height)
	{
		if(graph.chnlBgColor)
			fillBox(chnlRect.x,chnlRect.y,chnlRect.width,chnlRect.height,graph.chnlBgColor);
		if(chnlBordColor&&chnlBordLine)
			drawBox(chnlRect.x,chnlRect.y,chnlRect.width,chnlRect.height,chnlBordColor,chnlBordLine);
		ReadChnlObject obj;
		Channel *channel;
		float yPos=chnlRect.y;
		int j;
		for(obj=readChannels.objectAt(channelStart),j=0;j<_channelRead&&obj;obj=obj->next,j++)
		{
//todo: rafactoring needed
			channel=obj->value;
			chnl_str=channel->hidden?_T("  "):_T("* ");
			chnl_str+=channel->getName();
			if(channel->sym.length())
			{
				chnl_str+=_T(" - ");
				chnl_str+=channel->sym;
			}
			font->color=graph.getColor(channel);
			font->x=chnlRect.x+margin.x;
			yPos+=float(font->textMetrix.tmHeight);
			font->y=yPos;
			font->print(chnl_str);
		}
	}
	FlatComponent::draw();
}
void FlatGraph::showActiveInfo()
{
	Channel *marked=graph.marked;
	if(!marked)
	{
		tbColor.setText();
		tbThick.setText();
		tbStipplePattern.setText();
		tbStippleRepeat.setText();
		tbSymbol.setText();
		tbSymIndex.setText();
		tbSymOffsetX.setText();
		tbSymOffsetY.setText();
	}
	else
	{
		tbColor.setInt(marked->color);
		tbThick.setDouble(marked->thick);
		tbStipplePattern.setInt(marked->stipple_pattern);
		tbStippleRepeat.setInt(marked->stipple_repeat);
		tbSymbol.setText(marked->sym);
		tbSymIndex.setInt(marked->symIndex);
		tbSymOffsetX.setDouble(marked->symOffset.x);
		tbSymOffsetY.setDouble(marked->symOffset.y);
	}
}
void FlatGraph::build()
{
	float y;
	FlatObject obj1;

	readChannels.destroy();
	const List<Channel> *chnls=graph.getChannels();
	Iterator<Channel> iterChnl(chnls);
	while(iterChnl.next())
	{
		if(iterChnl.value->isReadable())
		{
			if(sorted)
			{
				BOOL found=FALSE;
				CSTR name=iterChnl.value->getName();
				Iterator<Channel*> iterRead(&readChannels);
				while(iterRead.next())
				{
					CSTR test=(*iterRead.value)->getName();
					if(_tcscmp(name,test)<=0)
					{
						readChannels.add(iterChnl.value,iterRead.obj);
						found=TRUE;
						break;
					}
				}
				if(found)
					continue;
			}
			readChannels.add(iterChnl.value);
		}
	}
	_channelRead=(chnlReadLines>=0)?chnlReadLines:min(readChannels.length,10);
	if(_channelRead)
	{
		if(_channelRead>0&&_channelRead<readChannels.length)
		{
			if(channelSpan=readChannels.length-_channelRead)
			{
				double tmp=INV(channelSpan);
				scroll.step=tmp;
				scroll.page=10*tmp;
			}
		}
		else
		{
			channelSpan=0;
		}
		channelStart=round(channelSpan*scroll.getRatio());
	}
	y=rect.height;
	chnlRect.height=0;
	chnlRect.y=0;
	if(toolbox&&font)
	{
		for(obj1=childs.first;obj1;obj1=obj1->next)
		{
			obj1->value->visible=TRUE;
			if(!obj1->value->font)
				obj1->value->font=font;
		}
		tbMarginX.setDouble(graph.marginChannel.x);
		tbMarginY.setDouble(graph.marginChannel.y);
		tbScaleX.setDouble(graph.scale.x);
		tbScaleY.setDouble(graph.scale.y);
		tbPrecX.setInt(graph.formatX.precision);
		tbPrecY.setInt(graph.formatY.precision);
		tbGridX.setInt(graph.grid.x);
		tbGridY.setInt(graph.grid.y);
		bExp.checked=graph.formatY.exposant;
		tbChnls.setInt(chnlReadLines);
		bSkip.checked=graph.skip;
		bSmooth.checked=graph.smooth;
		bSymShow.checked=graph.symShow;
		bLog.checked=graph.logscaleX;
		showActiveInfo();
		if(_channelRead&&font->loaded())
		{
			chnlRect.height+=float(font->textMetrix.tmHeight*_channelRead+margin.y);
			y-=chnlRect.height+margin.y;
			chnlRect.x=rect.x+margin.x;
			chnlRect.y=rect.y+y;
			chnlRect.width=rect.width-3*margin.x-0.5f*scroll.size.x;
			scroll.visible=TRUE;
			scroll.location.x=size.x-scroll.size.x-margin.x;
			scroll.location.y=y;
			scroll.size.y=chnlRect.height;
			y-=toolmarge+margin.y;
		}
		else
		{
			scroll.visible=FALSE;
		}
	}
	else
	{
		for(obj1=childs.first;obj1;obj1=obj1->next)
			obj1->value->visible=FALSE;
		graph.visible=TRUE;
	}
	y-=2*margin.y;
	graph.size.x=rect.width-2*margin.x;
	graph.size.y=y;
	graph.location.copy(margin);
	FlatComponent::build();
}

FlatList::FlatList():
	selectIndex(-1),
	selectBgColor(INT_BLUE),selectForColor(INT_WHITE)
{
	setCursor(IDC_HAND);
	isStatic=FALSE;
}
int FlatList::indexOf(CSTR option)
{
	if(option&&option[0])
	{
		int i;
		OptionObject obj;
		for(i=0,obj=options.first;obj;i++,obj=obj->next)
			if(obj->value.equals(option))
				return i;
	}
	return -1;
}
BOOL FlatList::add(CSTR option, int index)
{
	if(!option||!option[0]||indexOf(option)!=-1)
		return FALSE;
	OptionObject obj;
	obj=options.insert(index);
	if(!obj)
		return FALSE;
	obj->value=option;
	selectIndex=-1;
	return TRUE;
}
BOOL FlatList::rem(CSTR option)
{
	return rem(indexOf(option));
}
BOOL FlatList::rem(int index)
{
	if(index==-1)
		return FALSE;
	options.remove(index);
	selectIndex=-1;
	return TRUE;
}
void FlatList::clear()
{
	options.destroy();
	selectIndex=-1;
}
void FlatList::copy(const FlatList &list)
{
	FlatComponent::copy(list);
	selectBgColor=list.selectBgColor;
	selectForColor=list.selectForColor;
}

FlatChoice::FlatChoice():
	pressed(FALSE),
	height(0),
	oldZIndex(-1),
	listColor(INT_WCYAN)
{
}
void FlatChoice::draw()
{
	CSTR text;
	float x,y;
	int oldColor=0,i;
	
	if(pressed)
	{
		if(listColor)
			fillBox(rect.x,rect.y,rect.width,rect.height,listColor);
		if(bgColor)
			fillBox(rect.x,rect.y,rect.width,height,bgColor);
	}
	else if(bgColor)
		fillBox(rect.x,rect.y,rect.width,rect.height,bgColor);
	if(fontColor)
		font->color=fontColor;
	x=rect.x+margin.x;
	y=rect.y+margin.y+font->textMetrix.tmAscent;
	ListObject<String> *obj=options.objectAt(selectIndex);
	if(obj&&obj->value.length())
	{
		text=obj->value;
		font->x=x;
		font->y=y;
		switch(alignx)
		{
			case DIR_NONE:
				font->x+=0.5f*(rect.width-font->getTextWidth(text))-margin.x;
				break;
			case DIR_RIGHT:
				font->x+=rect.width-font->getTextWidth(text)-2*margin.x;
				break;
		}
		font->print(text);
	}
	if(pressed&&height&&options.length)
	{
		y+=height;
		for(i=0,obj=options.first;obj;i++,y+=height,obj=obj->next)
		{
			if(font&&obj->value.length())
			{
				if(i==selectIndex)
				{
					oldColor=font->color;
					if(selectBgColor)
						fillBox(rect.x,rect.y+(i+1)*height,rect.width,height,selectBgColor);
					if(selectForColor)
						font->color=selectForColor;
				}
				text=obj->value;
				font->x=x;
				font->y=y;
				switch(alignx)
				{
					case DIR_NONE:
						font->x+=0.5f*(rect.width-font->getTextWidth(text))-margin.x;
						break;
					case DIR_RIGHT:
						font->x+=rect.width-font->getTextWidth(text)-2*margin.x;
						break;
				}
				font->print(text);
				if(i==selectIndex&&selectForColor)
					font->color=oldColor;
			}
		}
	}
	float border=borderLine;
	if(activeState)
		border=activeLine;
	DWORD color=borderColor;
	if(disabled)
		color=disabledColor;
	if(color&&border)
	{
		if(pressed)
			drawBox(rect.x,rect.y,rect.width,height,color,border);
		drawBox(rect.x,rect.y,rect.width,rect.height,color,border);
	}
}
BOOL FlatChoice::resize()
{
	if(!font||!font->loaded()||!options.length)
		return FALSE;
	if(adjustSize)
		size.x=size.y=0;
	if(!size.x)
	{
		ListObject<String> *obj;
		for(obj=options.first;obj;obj=obj->next)
			size.x=maxval(font->getTextWidth(obj->value)+2*margin.x,size.x);
	}
	if(!size.y)
		size.y=font->textMetrix.tmHeight+2*margin.y;
	return TRUE;
}
BOOL FlatChoice::input(BYTE,UINT msg,const Mouse &mouse,const Keyboard &keyboard)
{
	if(msg==WM_KEYDOWN||msg==WM_LBUTTONDOWN)
	{
		if(msg==WM_KEYDOWN)
		{
			switch(keyboard.code)
			{
				case VK_RETURN:
				case VK_DOWN:
				case VK_BACK:
				case VK_UP:
					break;
				default:
					return FALSE;
			}
		}
		if(!pressed)
		{
			pressed=TRUE;
			if(options.length)
			{
				height=rect.height;
				rect.height*=options.length+1;
			}
			if(parent)
			{
				oldZIndex=zIndex;
				zIndex=parent->getMaxZIndex()+1;
			}
		}
		return TRUE;
	}
	else
	{
		if(pressed&&msg==WM_KEYUP)
		{
			switch(keyboard.code)
			{
			case VK_RETURN:
			case VK_DOWN:
				selectNextIndex();
				break;
			case VK_BACK:
			case VK_UP:
				selectPrevIndex();
				break;
			default:
				return FALSE;
			}
		}
		else if(pressed&&(msg==WM_LBUTTONUP||msg==WM_MOUSEMOVE))
		{
			double ratio=limit(rect.ratioy((float)mouse.y),0.0,1.0);
			int index=min(__int(ratio*(options.length+1))-1,options.length-1);
			if(index>=0)
				selectIndex=index;
			if(msg==WM_MOUSEMOVE)
				return TRUE;
		}
		else if(msg==WM_MOUSEWHEEL)
		{
			if(mouse.wheel>0)
				selectPrevIndex();
			else
				selectNextIndex();
		}
		else
		{
			return FALSE;
		}
		call(FLAT_ACTION_DEFAULT);
		pressed=FALSE;
		rect.height=height;
		zIndex=oldZIndex;
		return TRUE;
	}
}

BOOL FlatApplication::create()
{
	if(!Application::create())
		return FALSE;
	container.window=&window;
	container.commonFont=&font;
	font.setFace(_T("Courier New"));
	font.height=-16;
	add(&font);
	fd.hWnd=window.hWnd;
	screen.left=0;
	screen.right=800;
	screen.bottom=600;
	screen.top=0;
	screen.angle=45;
	screen.ratio=4.0f/3;
	screen.zNearOrtho=-1;
	screen.zFarOrtho=1;
	screen.zNearPers=0.01f;
	screen.zFarPers=100.0f;
	name=_T("FlatApplication");
	ShowCursor (TRUE);
	clearColor=0;
	return TRUE;
}
BOOL FlatApplication::glCreate()
{
	if(!Application::glCreate())
		return FALSE;
	ListObject<GLObject*> *obj;
	for(obj=objects.first;obj;obj=obj->next)
		obj->value->create(window.hDC);
	container.build();
	setClearColor(clearColor);
	return TRUE;
}
void FlatApplication::glDestroy()
{
	ListObject<GLObject*> *obj;
	for(obj=objects.first;obj;obj=obj->next)
		obj->value->destroy();
}
void FlatApplication::reshape ()
{
	glViewport (0, 0, window.width, window.height);
	screen.right=(float)window.width;
	screen.bottom=(float)window.height;
	screen.ratio=float(window.width)/window.height;
}
BOOL FlatApplication::input(BYTE type)
{
	if(	Application::input(type)||container.input(type,msg.message,mouse,keyboard))
		return TRUE;
	return FALSE;
}
void FlatApplication::draw()
{
	Lock lock(&draw_sect);
	glPushAttrib(GL_ENABLE_BIT);
	// 3D
	glEnable(GL_DEPTH_TEST);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective (screen.angle,
					screen.ratio,
					screen.zNearPers,
					screen.zFarPers);
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
	draw3D();
	// 2D
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	glOrtho(screen.left,
			screen.right,
			screen.bottom,
			screen.top,
			screen.zNearOrtho,
			screen.zFarOrtho);
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity();
	draw2D();
	glLoadIdentity ();
	container.draw();
	glPopAttrib();
	draw2Dpost();
}
BOOL FlatApplication::screenshot(	Image *image,
									GLint x, GLint y,
									GLsizei w, GLsizei h,
									GLenum readbuff,
									GLint align)
{
	//h=w=40;
	if (!image)
		return FALSE;
	if(!w||w>window.width-x)
		w=window.width-x;
	if(!h||h>window.height-y)
		h=window.height-y;
	if(h<=0||w<=0)
		return FALSE;
	y=window.height-y-h;
	if(!image->setSize(w,h,PIXEL_COMP_RGB))
		return FALSE;
	if(readbuff)
		glReadBuffer(readbuff);
	if(align<0)
		setMemAlign(w,TRUE);
	else if(align>0)
		setMemAlign(align,TRUE);
	glReadPixels(	x,y,w,h,
					GL_RGB,GL_UNSIGNED_BYTE,
					image->data);
	return !glGetError();
}

}; // namespace Lio